#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <pthread.h>

#define N 128

#define ERR_LOG(errmsg) do{\
                             perror(errmsg);\
                             exit(1);\
                          }while(0)

typedef struct{
    struct sockaddr_in addr;
    int acceptfd;
}MSG;

void *thread_work(void *arg){
    char buf[N] = "";
    ssize_t bytes;
    MSG msg = *(MSG *)arg;
    while(1){
        if((bytes = recv(msg.acceptfd,buf,N,0))<0)
        {
            ERR_LOG("fail to recv");
        }
        else if(bytes == 0)
        {
            printf("The client quited\n");
            exit(1);
        }

        if(strncmp(buf,"quit",4)==0)
        {
            exit(0);
        }

        printf("from client: %s\n",buf);

        strcat(buf,"*_*");//将参数1和参数2拼接，并将结果返回到参数1中。

        if(send(msg.acceptfd,buf,N,0)<0)
        {
            ERR_LOG("fail to send");
        }
    }
}


int main(int argc,char const *argv[]){
    if(argc < 3){
        fprintf(stderr,"Usage: %s [ip] [port]\n",argv[0]);
	exit(1);
    }

    //第一步：创建套接字
    int sockfd;
    if((sockfd = socket(AF_INET,SOCK_STREAM,0))==-1){
       ERR_LOG("fail to socket");
    }

    //将套接字设置为允许重复使用本机地址(因为没有多台设备充当客户端)或者设置为端口复用
    int on=1;
    if(setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0){
       ERR_LOG("fail to setsockopt");
    }

    //第二步：将套接字与服务器网络信息结构体绑定
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));
    
    if(bind(sockfd,(struct sockaddr *)&serveraddr,addrlen)==-1){
        ERR_LOG("fail to bind");
    }

    //第三步：将套接字设置为被动监听状态
    if(listen(sockfd,10)==-1){
        ERR_LOG("fail to listen");
    }

    //第四步：阻塞等待客户端的连接请求
    int acceptfd;//这个变量用来接收accept()返回的套接字，每个套接字代表与一个客户端的连接
    struct sockaddr_in clientaddr;
  
    //第五步：循环接收客户端，每接收一个客户端创建一个线程
    while(1){
        if((acceptfd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen))==-1){
            ERR_LOG("fail to accept");
        }

        //打印客户端信息
        printf("[%s ‐ %d]: \n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port) );
        //创建子线程与客户端进行通信
        MSG msg;
        msg.addr = clientaddr;
        msg.acceptfd = acceptfd;
        pthread_t thread;
        if(pthread_create(&thread,NULL,thread_work,&msg)!=0){
            ERR_LOG("fail to pthread_create");
        }
        pthread_detach(thread);
    }
    close(sockfd);
    close(acceptfd);
    return 0;
}
